import os
import traceback
from io import BytesIO

import qrcode
import requests
import requests_cache
import urllib3
from PIL import Image, ImageDraw, ImageFont

import C

NO_IMG = Image.open(os.path.join(os.path.dirname(__file__), 'resource/upload/default.png')).convert('RGBA')
requests_cache.install_cache(C.STORE_DB + '/cache')


def fixAutoRotate(image):
    exif = image.getexif()
    # 如果存在 Exif 信息并且包含旋转信息（Orientation=6 表示需要逆时针旋转 90 度）
    if exif and 0x0112 in exif:
        orientation = exif[0x0112]
        rotations = {
            3: 180,
            6: 270,
            8: 90
        }
        # 执行旋转操作
        if orientation in rotations:
            image = image.rotate(rotations[orientation])
    return image

def fetchImg(url=''):
    try:
        if url.startswith('store/upload/'):
            if os.path.exists(f'data/{url}'):
                return fixAutoRotate(Image.open(f'data/{url}').convert('RGBA'))
            else:
                return NO_IMG
        r = requests.get(url, timeout=0.5)
        return fixAutoRotate(Image.open(BytesIO(r.content)).convert('RGBA'))
    except urllib3.exceptions.ReadTimeoutError:
        print(f'ERROR: fetch image timeout: url={url}')
        return None
    except Exception:
        traceback.print_exc()
        return NO_IMG


def drawImg(d, bg):
    url, w, h, x, y = d['v'], d['w'], d['h'], d['x'], d['y']
    try:
        img = fetchImg(url)
        if img is None: return
        img = img.resize((w, h), Image.LANCZOS)
        # img = createCenteredImage(img, w, h)
        img, x, y = rotate(img, d)
        bg.paste(img, (x, y), img)
    except Exception:
        traceback.print_exc()


def createCenteredImage(img, width, height):
    if img.width < width and img.height < height:
        return img.resize((width, height), Image.LANCZOS)
    scale_factor = min(width / img.width, height / img.height)
    new_width = int(img.width * scale_factor)
    new_height = int(img.height * scale_factor)
    resized_image = img.resize((new_width, new_height), Image.LANCZOS)
    new_image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
    horizontal_offset = int((width - new_width) / 2)
    vertical_offset = int((height - new_height) / 2)
    new_image.paste(resized_image, (horizontal_offset, vertical_offset))
    return new_image


def drawBg(d):
    url = str(d['bgUrl'])
    w, h, bgc, use = d['w'], d['h'], d['bgc'], d['use']
    bgc = '#fafbfc' if not bgc or bgc == '' else bgc
    if use == 'bgc' or url.strip() == '':
        img = Image.new('RGBA', (w, h), bgc)
    else:
        img = fetchImg(url)
    img = img.resize((w, h), Image.LANCZOS)
    return img


def getFont(d):
    fn, size = d['fn'], d['s']
    font = C.STORE_FONTS + fn
    if fn == '':
        font = C.STORE_FONTS + '01/0d44d315557a4a25.woff'
    if not os.path.exists(font):
        font = C.STORE_FONTS + '01/0d44d315557a4a25.woff'
        if not os.path.exists(font):
            font = C.STORE_FONTS + '0d44d315557a4a25.woff'
    return ImageFont.truetype(font, size)


class TextItem:

    def __init__(self):
        self.width = 0
        self.text = ''

    def add(self, text, ls):
        self.text += text
        self.width += ls


def drawText(d, bg):
    font = getFont(d)
    v, w, h, x, y, c, bgc, bius, ls, vs = (d['v'], d['w'], d['h'], d['x'], d['y'], d['c'], d['bgc'],
                                           d.get('bius', ''), d.get('ls', 0), d.get('vs', 0))
    a = d.get('a', 'left')
    # wrap = d.get('wrap', False) or '\n' in v    # 是否需要换行
    wrap = True  # 默认需要换行
    c = '#000000' if not c or c == '' else c
    bgc = '#fff0' if not bgc or bgc == '' else bgc
    img = Image.new("RGBA", (w, h), bgc)
    draw = ImageDraw.Draw(img)  # type:ImageDraw.ImageDraw
    # draw.fontmode = "1"
    stroke_width = 1 if 'b' in bius else 0  # 加粗

    lines = []
    tl = TextItem()
    for s in v:

        # 字符本身存在换行的情况
        if '\n' == s:
            lines.append(tl)
            tl = TextItem()
            continue

        sw = font.getlength(s)
        tl.width += sw
        # print(f'{s}:\tsw={sw}, width={tl.width}, text={tl.text}')

        if tl.width > w:
            tl.width -= sw
            lines.append(tl)
            # 执行换行
            tl = TextItem()
            tl.width += sw
            tl.add(s, ls)
        else:
            tl.add(s, ls)
    if tl.width > 0:
        lines.append(tl)

    sy = 0
    sx = 0
    size = d['s']
    fm = font.getmetrics()
    ascent = fm[0]
    descent = fm[1]
    height = ascent + descent
    rate = height / size
    lineheight = size + vs

    if abs(height - size) > 1:
        sy = (size - height) / 2

    if vs != 0:
        sy += vs / 2

    # print(f'ascent={ascent}, descent={descent}, height={height}, size={size}, sy={sy}')

    boxH = 0
    for t in lines:
        line = t.text
        sw = t.width

        if sw <= w:
            sx = 0
            if "center" == a:
                sx += (w - sw) / 2
            elif "right" == a:
                sx += w - sw

        sx0 = sx
        for s in line:
            draw.text((sx0, sy), s, fill=c, font=font, stroke_width=stroke_width)
            sx0 += font.getlength(text=s)
            sx0 += ls

        # 绘制删除线、下划线
        if 's' in bius or 'u' in bius:
            lw = sx + sw  # 计算宽度
            strokeWidth = int(size / 10)
            ly = sy + ascent + strokeWidth
            if 's' in bius:
                ly -= int(size * 0.4)
            lxy = (sx, ly, lw, ly)  # 计算先
            draw.line(lxy, fill=c, width=strokeWidth + stroke_width)

        sy += lineheight

        # 检查高度是否溢出
        boxH += lineheight  # 累计盒子高度
        if boxH > h:
            break

        # 不自动换行，直接退出循环
        # if not wrap:
        #     break

    # 斜体方案一，在多行和文字竖排时效果不佳，暂时无法解决，如果对斜体有要求，推荐使用Java版本
    # https://encyclopedia.thefreedictionary.com/Affine+transformation
    if 'i' in bius:
        img = img.transform((w, h), Image.AFFINE, (1, 0.2, 0, 0, 1, 0), Image.BICUBIC, fill=1)
        # 修复斜体导致图片被裁剪的问题
        img2 = Image.new("RGBA", (w, h), bgc)
        img2.paste(img, (0, 0), img)
        img = img2
    img, x, y = rotate(img, d)
    if img is not None: bg.paste(img, (x, y), img)


def drawQrCode(d, bg):
    v, w, h, x, y, c, bgc = d['v'], d['w'], d['h'], d['x'], d['y'], d['c'], d['bgc']
    c = '#010203' if not c or c == '' else c
    bgc = '#ffffff' if not bgc or bgc == '' else bgc
    p = d.get('p', 0)
    qr = qrcode.QRCode(version=2, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=10, border=p)
    qr.add_data(v)
    img = qr.make_image(fill_color=c, back_color=bgc)
    img = img.resize((w, h), Image.LANCZOS)
    img = img.convert('RGBA')
    img, x, y = rotate(img, d)
    bg.paste(img, (x, y), img)


def drawAvatar(d, bg):
    v, w, h, x, y, c = d['v'], d['w'], d['h'], d['x'], d['y'], d['c']
    c = '#ffffff' if not c or c == '' else c
    img = fetchImg(v)
    if img is None: return
    bigsize = (img.size[0] * 3, img.size[1] * 3)
    mask = Image.new('L', bigsize, 0)
    draw = ImageDraw.Draw(mask)
    draw.ellipse((0, 0) + bigsize, fill=255)
    mask = mask.resize(img.size, Image.LANCZOS)
    img.putalpha(mask)
    img = img.resize((w, h), Image.LANCZOS)
    mask = Image.new('RGBA', bigsize)
    draw = ImageDraw.Draw(mask)
    draw.ellipse((0, 0) + bigsize, outline=c, width=4 * 3)
    mask = mask.resize(img.size, Image.LANCZOS)
    img.paste(mask, (0, 0), mask)
    img, x, y = rotate(img, d)
    bg.paste(img, (x, y), img)
    pass


def rotate(img, d):
    w, h, x, y, angle = d['w'], d['h'], d['x'], d['y'], d.get('angle', 0)
    if 0 < angle < 360:
        # img = img.rotate(360 - angle, expand=True, resample=Image.NEAREST)     # 效果很粗糙
        img = img.rotate(360 - angle, expand=True, resample=Image.BILINEAR)  # 效果不错
        # img = img.rotate(360 - angle, expand=True, resample=Image.BICUBIC)  # 文字效果没有锯齿，不过边框效果欠佳
        x, y = x + int(w / 2 - img.width / 2), y + int(h / 2 - img.height / 2)
    return img, x, y


def draw(data):
    bg = drawBg(data)
    for d in data['items']:
        type = d['t']
        if 'text' == type:
            drawText(d, bg=bg)
        if 'image' == type:
            drawImg(d, bg=bg)
        if 'avatar' == type:
            drawAvatar(d, bg=bg)
        if 'qrcode' == type:
            v = d.get('v', '')
            if v.startswith('img:'):
                d['v'] = v[4:]  # 同一套模板适配普通二维码和小程序码
                drawImg(d, bg=bg)
            else:
                drawQrCode(d, bg=bg)
    if data['type'] != 'png':
        bg = bg.convert("RGB")
    return bg


def drawio(d, scale=1):
    buf = BytesIO()
    img = draw(d)
    type = 'jpeg' if d['type'] == 'jpg' else d['type']
    mimetype = f"image/{type}"
    # 压缩
    if scale < 1:
        w, h = img.size[0], img.size[1]
        img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
    if d['type'] == 'pdf':
        img.save(buf, type, resolution=100.0, all_save=True, append_images=[img])
        mimetype = 'application/pdf'
    else:
        img.save(buf, type, quality=d['quality'], progressive=True)
    buf.seek(0)
    return buf, mimetype

def getImageSize(body):
    try:
        img = Image.open(BytesIO(body)).convert('RGBA')
        return img.size
    except:
        return (0, 0)
